package com.jorado.apollo;

import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.openapi.client.constant.ApolloOpenApiConstants;
import com.ctrip.framework.apollo.util.ExceptionUtil;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.jorado.apollo.dto.AppDTO;
import com.jorado.apollo.dto.AppNamespaceDTO;
import com.jorado.apollo.dto.ItemDTO;
import com.jorado.apollo.model.NamespaceReleaseModel;
import com.jorado.apollo.model.NamespaceTextModel;
import com.jorado.apollo.model.Result;
import com.jorado.apollo.model.ResultStatus;
import com.jorado.apollo.service.*;
import com.jorado.apollo.util.DateUtils;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;

/**
 * 阿波罗超级客户端代理
 * 基于模拟登陆方式获取所有操作权限,慎用
 */
public class ApolloSuperClientProxy {
    private static final Logger LOGGER = LoggerFactory.getLogger(ApolloSuperClientProxy.class);
    private ApolloClientOptions clientOptions;

    private final UserApiService userService;
    private final AppApiService appService;
    private final ItemApiService itemService;
    private final ReleaseApiService releaseService;
    private final NamespaceApiService namespaceService;

    private ApolloSuperClientProxy(ApolloClientOptions clientOptions, RequestConfig requestConfig) {
        this.clientOptions = clientOptions;
        BasicCookieStore cookieStore = new BasicCookieStore();
        CloseableHttpClient client = HttpClients.custom().setDefaultRequestConfig(requestConfig)
                .setDefaultHeaders(Lists.newArrayList(new BasicHeader("Authorization", clientOptions.getToken())))
                .setDefaultCookieStore(cookieStore)
                .build();

        String baseUrl = clientOptions.getPortalUrl();
        userService = new UserApiService(client, cookieStore, baseUrl);
        //先登陆
        this.login();
        appService = new AppApiService(client, baseUrl);
        namespaceService = new NamespaceApiService(client, baseUrl);
        itemService = new ItemApiService(client, baseUrl);
        releaseService = new ReleaseApiService(client, baseUrl);
    }

    public static ApolloClientBuilder newBuilder(String portalUrl, String userName, String password) {
        return new ApolloClientBuilder(portalUrl, userName, password);
    }

    /**
     * 获取集群信息
     *
     * @param appIds
     * @return
     */
    public Result<List<AppDTO>> getAppsInfo(String... appIds) {
        try {
            List<AppDTO> dto = this.appService.getAppsInfo(Arrays.asList(appIds));
            return Result.ofSuccess(dto);
        } catch (Exception ex) {
            LOGGER.error(String.format("get apps info error,appIds:%s,option:%s", appIds, getClientOptions().toString()), ex);
            return errorResult(ex);
        }
    }

    /**
     * 创建配置
     *
     * @param namespace
     * @param key
     * @param value
     * @param comment
     * @return
     */
    public Result createConfig(String namespace, String key, String value, String comment) {
        try {
            ItemDTO dto = new ItemDTO();
            dto.setKey(key);
            dto.setValue(value);
            dto.setComment(comment);
            this.itemService.createItem(clientOptions.getAppId(), clientOptions.getEnv(), clientOptions.getClusterName(), namespace, dto);
            return Result.OK;
        } catch (Exception ex) {
            LOGGER.error(String.format("create config error,namespace:%s,key:%s,value:%s,option:%s", namespace, key, value, getClientOptions().toString()), ex);
            return errorResult(ex);
        }
    }

    /**
     * 创建配置
     *
     * @param namespace
     * @param key
     * @param value
     * @return
     */
    public Result createConfig(String namespace, String key, String value) {
        return createConfig(namespace, key, value, DateUtils.timeNow());
    }

    /**
     * 通过文本形式批量修改配置
     *
     * @param namespace
     * @return
     */
    public Result updateConfigs(String namespace, Map<String, String> configs) {
        List<String> configList = new ArrayList<>();
        configs.entrySet().forEach(n -> configList.add(n.getKey() + "=" + n.getValue()));
        String configText = StringUtils.join(configList, "\n");
        return updateConfigs(namespace, configText);
    }

    /**
     * 通过文本形式批量修改配置
     *
     * @param namespace
     * @return
     */
    public Result updateConfigs(String namespace, String configText) {
        try {
            NamespaceTextModel model = new NamespaceTextModel();
            model.setFormat("properties");
            model.setConfigText(configText);
            this.itemService.updateItems(clientOptions.getAppId(), clientOptions.getEnv(), clientOptions.getClusterName(), namespace, model);
            return Result.OK;
        } catch (Exception ex) {
            LOGGER.error(String.format("create config error,namespace:%s,configText:%s,option:%s", namespace, configText, getClientOptions().toString()), ex);
            return errorResult(ex);
        }
    }

    /**
     * 创建命名空间
     *
     * @param namespace
     * @return
     */
    public Result createNamespace(String namespace) {
        return createNamespace(namespace, false);
    }

    /**
     * 创建命名空间
     *
     * @param namespace
     * @param isPublic
     * @return
     */
    public Result createNamespace(String namespace, boolean isPublic) {
        try {
            AppNamespaceDTO dto = new AppNamespaceDTO();
            dto.setAppId(this.clientOptions.getAppId());
            dto.setName(namespace);
            dto.setPublic(isPublic);
            dto.setDataChangeCreatedBy(this.clientOptions.getUserName());
            dto.setFormat("properties");
            dto.setComment("create by [" + this.clientOptions.getUserName() + "] at:" + DateUtils.timeNow());
            this.namespaceService.createNamespace(dto);
            return Result.OK;
        } catch (Exception ex) {
            LOGGER.error(String.format("create namespace error,namespaceName:%s,option:%s", namespace, getClientOptions().toString()), ex);
            return errorResult(ex);
        }
    }

    /**
     * 删除namespace
     *
     * @param namespaceNames
     * @return
     */
    public Result deleteNamespace(String... namespaceNames) {
        try {
            for (String namespaceName : namespaceNames) {
                this.namespaceService.deleteNamespace(clientOptions.getAppId(), namespaceName);
            }
            return Result.OK;
        } catch (Exception ex) {
            LOGGER.error(String.format("delete namespace error,namespaceNames:%s,option:%s", namespaceNames, getClientOptions().toString()), ex);
            return errorResult(ex);
        }
    }

    /**
     * 删除namespace
     *
     * @param namespaceNames
     * @return
     */
    public Result deleteNamespace(Collection<String> namespaceNames) {
        String[] array = new String[namespaceNames.size()];
        return deleteNamespace(namespaceNames.toArray(array));
    }

    /**
     * 发布配置
     *
     * @param namespace
     * @param title
     * @return
     */
    public Result publishConfig(String namespace, String title) {
        try {
            NamespaceReleaseModel model = new NamespaceReleaseModel();
            model.setReleaseTitle(title);
            model.setReleaseComment("publish by [" + this.clientOptions.getUserName() + "] at:" + DateUtils.timeNow());
            this.releaseService.release(this.clientOptions.getAppId(), this.clientOptions.getEnv(), this.clientOptions.getClusterName(), namespace, model);
            return Result.OK;
        } catch (Exception ex) {
            LOGGER.error(String.format("public config error,namespace:%s,title:%s,option:%s", namespace, title, getClientOptions().toString()), ex);
            return errorResult(ex);
        }
    }

    /**
     * 用户登陆
     *
     * @return
     */
    private void login() {
        LOGGER.info("user {} begin login....", clientOptions.getUserName());
        String sessionId = userService.login(clientOptions.getUserName(), clientOptions.getPassword());
        if (StringUtils.isBlank(sessionId)) {
            throw new RuntimeException("login failed !!");
        }
        LOGGER.info("login success,sessionid:{}", sessionId);
    }

    private Result errorResult(String... args) {
        StringBuilder messageBuilder = new StringBuilder();
        messageBuilder.append("option:" + getClientOptions().toString());
        for (String s : args) {
            messageBuilder.append(",");
            messageBuilder.append(s);
        }
        return Result.ofFail(ResultStatus.CONFIG_ERROR, messageBuilder.toString());
    }

    private Result errorResult(Throwable ex) {
        return errorResult(ExceptionUtil.getDetailMessage(ex));
    }

    public ApolloClientOptions getClientOptions() {
        return clientOptions;
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder("{");
        sb.append("\"clientOptions\":")
                .append(clientOptions);
        sb.append('}');
        return sb.toString();
    }

    public static class ApolloClientBuilder {
        private String portalUrl;
        private String userName;
        private String password;

        private int connectTimeout = -1;
        private int readTimeout = -1;

        private String appId;
        private String env = "dev";
        private String clusterName = "default";

        public ApolloClientBuilder(String portalUrl, String userName, String password) {
            this.portalUrl = portalUrl;
            this.userName = userName;
            this.password = password;
        }

        public ApolloClientBuilder withConnectTimeout(int connectTimeout) {
            this.connectTimeout = connectTimeout;
            return this;
        }

        public ApolloClientBuilder withReadTimeout(int readTimeout) {
            this.readTimeout = readTimeout;
            return this;
        }

        public ApolloClientBuilder withAppId(String appId) {
            this.appId = appId;
            return this;
        }

        public ApolloClientBuilder withEnv(String env) {
            this.env = env;
            return this;
        }

        public ApolloClientBuilder withClusterName(String clusterName) {
            this.clusterName = clusterName;
            return this;
        }

        public ApolloSuperClientProxy build() {

            Preconditions.checkArgument(!Strings.isNullOrEmpty(appId), "appid should not be null or empty!");
            Preconditions.checkArgument(!Strings.isNullOrEmpty(env), "env should not be null or empty!");
            Preconditions.checkArgument(!Strings.isNullOrEmpty(clusterName), "cluster name should not be null or empty!");
            Preconditions.checkArgument(!Strings.isNullOrEmpty(userName), "user name should not be null or empty!");
            Preconditions.checkArgument(!Strings.isNullOrEmpty(password), "user password should not be null or empty!");

            if (connectTimeout < 0) {
                connectTimeout = ApolloOpenApiConstants.DEFAULT_CONNECT_TIMEOUT;
            }

            if (readTimeout < 0) {
                readTimeout = ApolloOpenApiConstants.DEFAULT_READ_TIMEOUT;
            }

            RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(connectTimeout)
                    .setSocketTimeout(readTimeout).build();

            ApolloClientOptions clientOptions = new ApolloClientOptions();
            clientOptions.setPortalUrl(portalUrl);
            clientOptions.setAppId(appId);
            clientOptions.setClusterName(clusterName);
            clientOptions.setEnv(env);
            clientOptions.setUserName(userName);
            clientOptions.setPassword(password);
            return new ApolloSuperClientProxy(clientOptions, requestConfig);
        }
    }
}